/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/perfetto_cmd/perfetto_cmd.h"

#include <sys/sendfile.h>

#include <cinttypes>

#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/scoped_mmap.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/uuid.h"
#include "perfetto/protozero/proto_decoder.h"
#include "perfetto/tracing/core/forward_decls.h"
#include "src/android_internal/incident_service.h"
#include "src/android_internal/lazy_library_loader.h"
#include "src/android_internal/tracing_service_proxy.h"
#include "src/android_stats/statsd_logging_helper.h"

#include "protos/perfetto/config/trace_config.gen.h"

#include "protos/perfetto/trace/trace.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"

namespace perfetto {
namespace {

// traced runs as 'user nobody' (AID_NOBODY), defined here:
// https://cs.android.com/android/platform/superproject/+/android-latest-release:system/core/libcutils/include/private/android_filesystem_config.h;l=203;drc=f5b540e2b7b9b325d99486d49c0ac57bdd0c5344
// We only trust packages written by traced.
static constexpr int32_t kTrustedUid = 9999;

// Directory for local state and temporary files. This is automatically
// created by the system by setting setprop persist.traced.enable=1.
const char* kStateDir = "/data/misc/perfetto-traces";

constexpr int64_t kSendfileTimeoutNs = 10UL * 1000 * 1000 * 1000;  // 10s

}  // namespace

void PerfettoCmd::SaveTraceIntoIncidentOrCrash() {
  PERFETTO_CHECK(save_to_incidentd_);

  const auto& cfg = trace_config_->incident_report_config();
  PERFETTO_CHECK(!cfg.destination_package().empty());
  PERFETTO_CHECK(!cfg.skip_incidentd());

  if (bytes_written_ == 0) {
    LogUploadEvent(PerfettoStatsdAtom::kNotUploadingEmptyTrace);
    PERFETTO_LOG("Skipping write to incident. Empty trace.");
    return;
  }

  // Save the trace as an incident.
  SaveOutputToIncidentTraceOrCrash();

  // Skip the trace-uuid link for traces that are too small. Realistically those
  // traces contain only a marker (e.g. seized_for_bugreport, or the trace
  // expired without triggers). Those are useless and introduce only noise.
  if (bytes_written_ > 4096) {
    base::Uuid uuid(uuid_);
    PERFETTO_LOG("go/trace-uuid/%s name=\"%s\" size=%" PRIu64,
                 uuid.ToPrettyString().c_str(),
                 trace_config_->unique_session_name().c_str(), bytes_written_);
  }

  // Ask incidentd to create a report, which will read the file we just
  // wrote.
  PERFETTO_LAZY_LOAD(android_internal::StartIncidentReport, incident_fn);
  PERFETTO_CHECK(incident_fn(cfg.destination_package().c_str(),
                             cfg.destination_class().c_str(),
                             cfg.privacy_level()));
}

// static
base::Status PerfettoCmd::ReportTraceToAndroidFramework(
    int trace_fd,
    uint64_t trace_size,
    const base::Uuid& uuid,
    const std::string& unique_session_name,
    const protos::gen::TraceConfig_AndroidReportConfig& report_config,
    bool statsd_logging) {
  auto log_upload_event_fn = [statsd_logging, &uuid](PerfettoStatsdAtom atom) {
    if (statsd_logging) {
      android_stats::MaybeLogUploadEvent(atom, uuid.lsb(), uuid.msb());
    }
  };

  if (report_config.reporter_service_class().empty() ||
      report_config.reporter_service_package().empty()) {
    return base::ErrStatus("Invalid 'android_report_config'");
  }
  if (report_config.skip_report()) {
    return base::ErrStatus("'android_report_config.skip_report' is true.");
  }

  if (trace_size == 0) {
    log_upload_event_fn(PerfettoStatsdAtom::kCmdFwReportEmptyTrace);
    PERFETTO_LOG("Skipping reporting trace to Android. Empty trace.");
    return base::OkStatus();
  }

  log_upload_event_fn(PerfettoStatsdAtom::kCmdFwReportBegin);
  base::StackString<128> self_fd("/proc/self/fd/%d", trace_fd);
  base::ScopedFile fd(base::OpenFile(self_fd.c_str(), O_RDONLY | O_CLOEXEC));
  if (!fd) {
    return base::ErrStatus(
        "Failed to dup fd when reporting to Android: %s (errno: %d)",
        strerror(errno), errno);
  }

  PERFETTO_LAZY_LOAD(android_internal::ReportTrace, report_fn);
  bool report_ok = report_fn(report_config.reporter_service_package().c_str(),
                             report_config.reporter_service_class().c_str(),
                             fd.release(), uuid.lsb(), uuid.msb(),
                             report_config.use_pipe_in_framework_for_testing());

  if (!report_ok) {
    return base::ErrStatus("Failed in 'android_internal::ReportTrace'");
  }

  // Skip the trace-uuid link for traces that are too small. Realistically those
  // traces contain only a marker (e.g. seized_for_bugreport, or the trace
  // expired without triggers). Those are useless and introduce only noise.
  if (trace_size > 4096) {
    PERFETTO_LOG("go/trace-uuid/%s name=\"%s\" size=%" PRIu64,
                 uuid.ToPrettyString().c_str(), unique_session_name.c_str(),
                 trace_size);
  }
  log_upload_event_fn(PerfettoStatsdAtom::kCmdFwReportHandoff);
  return base::OkStatus();
}

void PerfettoCmd::ReportTraceToAndroidFrameworkOrCrash() {
  PERFETTO_CHECK(trace_out_stream_);
  int trace_fd = fileno(*trace_out_stream_);
  base::Uuid uuid(uuid_);
  base::Status status = ReportTraceToAndroidFramework(
      trace_fd, bytes_written_, uuid, trace_config_->unique_session_name(),
      trace_config_->android_report_config(), statsd_logging_);
  if (!status.ok()) {
    PERFETTO_FATAL("ReportTraceToAndroidFramework: %s", status.c_message());
  }
}

// Open a staging file (unlinking the previous instance), copy the trace
// contents over, then rename to a final hardcoded path (known to incidentd).
// Such tracing sessions should not normally overlap. We do not use unique
// unique filenames to avoid creating an unbounded amount of files in case of
// errors.
void PerfettoCmd::SaveOutputToIncidentTraceOrCrash() {
  LogUploadEvent(PerfettoStatsdAtom::kUploadIncidentBegin);
  base::StackString<256> kIncidentTracePath("%s/incident-trace", kStateDir);

  base::StackString<256> kTempIncidentTracePath("%s.temp",
                                                kIncidentTracePath.c_str());

  PERFETTO_CHECK(unlink(kTempIncidentTracePath.c_str()) == 0 ||
                 errno == ENOENT);

  // TODO(b/155024256) These should not be necessary (we flush when destroying
  // packet writer and sendfile should ignore file offset) however they should
  // not harm anything and it will help debug the linked issue.
  PERFETTO_CHECK(fflush(*trace_out_stream_) == 0);
  PERFETTO_CHECK(fseek(*trace_out_stream_, 0, SEEK_SET) == 0);

  // SELinux constrains the set of readers.
  base::ScopedFile staging_fd = base::OpenFile(kTempIncidentTracePath.c_str(),
                                               O_CREAT | O_EXCL | O_RDWR, 0666);
  PERFETTO_CHECK(staging_fd);

  int fd = fileno(*trace_out_stream_);
  off_t offset = 0;
  size_t remaining = static_cast<size_t>(bytes_written_);

  // Count time in terms of CPU to avoid timeouts due to suspend:
  base::TimeNanos start = base::GetThreadCPUTimeNs();
  for (;;) {
    errno = 0;
    PERFETTO_DCHECK(static_cast<size_t>(offset) + remaining == bytes_written_);
    auto wsize = PERFETTO_EINTR(sendfile(*staging_fd, fd, &offset, remaining));
    if (wsize < 0) {
      PERFETTO_FATAL("sendfile() failed wsize=%zd, off=%" PRId64
                     ", initial=%" PRIu64 ", remaining=%zu",
                     wsize, static_cast<int64_t>(offset), bytes_written_,
                     remaining);
    }
    remaining -= static_cast<size_t>(wsize);
    if (remaining == 0) {
      break;
    }
    base::TimeNanos now = base::GetThreadCPUTimeNs();
    if (now < start || (now - start).count() > kSendfileTimeoutNs) {
      PERFETTO_FATAL("sendfile() timed out wsize=%zd, off=%" PRId64
                     ", initial=%" PRIu64
                     ", remaining=%zu, start=%lld, now=%lld",
                     wsize, static_cast<int64_t>(offset), bytes_written_,
                     remaining, static_cast<long long int>(start.count()),
                     static_cast<long long int>(now.count()));
    }
  }

  staging_fd.reset();
  PERFETTO_CHECK(
      rename(kTempIncidentTracePath.c_str(), kIncidentTracePath.c_str()) == 0);
  // Note: not calling fsync(2), as we're not interested in the file being
  // consistent in case of a crash.
  LogUploadEvent(PerfettoStatsdAtom::kUploadIncidentSuccess);
}

// static
base::ScopedFile PerfettoCmd::CreateUnlinkedTmpFile() {
  // If we are tracing to DropBox, there's no need to make a
  // filesystem-visible temporary file.
  auto fd = base::OpenFile(kStateDir, O_TMPFILE | O_RDWR, 0600);
  if (!fd)
    PERFETTO_PLOG("Could not create a temporary trace file in %s", kStateDir);
  return fd;
}

// static
std::optional<TraceConfig> PerfettoCmd::ParseTraceConfigFromMmapedTrace(
    base::ScopedMmap mmapped_trace) {
  PERFETTO_CHECK(mmapped_trace.IsValid());

  protozero::ProtoDecoder trace_decoder(mmapped_trace.data(),
                                        mmapped_trace.length());

  for (auto packet = trace_decoder.ReadField(); packet;
       packet = trace_decoder.ReadField()) {
    if (packet.id() != protos::pbzero::Trace::kPacketFieldNumber ||
        packet.type() !=
            protozero::proto_utils::ProtoWireType::kLengthDelimited) {
      return std::nullopt;
    }

    protozero::ProtoDecoder packet_decoder(packet.as_bytes());

    auto trace_config_field = packet_decoder.FindField(
        protos::pbzero::TracePacket::kTraceConfigFieldNumber);
    if (!trace_config_field)
      continue;

    auto trusted_uid_field = packet_decoder.FindField(
        protos::pbzero::TracePacket::kTrustedUidFieldNumber);
    if (!trusted_uid_field)
      continue;

    int32_t uid_value = trusted_uid_field.as_int32();

    if (uid_value != kTrustedUid)
      continue;

    TraceConfig trace_config;
    if (trace_config.ParseFromArray(trace_config_field.data(),
                                    trace_config_field.size())) {
      return trace_config;
    }
  }

  return std::nullopt;
}

}  // namespace perfetto
